home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Ultra Pack
/
UltraComputing Partner Applications.iso
/
SunLabs
/
tclTK
/
src
/
tk4.0
/
tkEvent.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-06-21
|
52KB
|
1,853 lines
/*
* tkEvent.c --
*
* This file provides basic event-managing facilities, whereby
* procedure callbacks may be attached to certain events. It
* also contains the command procedures for the commands "after"
* and "fileevent", plus abridged versions of "tkwait" and
* "update", for use with Tk_EventInit.
*
* Copyright (c) 1990-1994 The Regents of the University of California.
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
static char sccsid[] = "@(#) tkEvent.c 1.97 95/06/21 15:16:33";
#include "tkPort.h"
#include "tkInt.h"
#include <errno.h>
/*
* For each timer callback that's pending, there is one record
* of the following type, chained together in a list sorted by
* time (earliest event first).
*/
typedef struct TimerEvent {
struct timeval time; /* When timer is to fire. */
void (*proc) _ANSI_ARGS_((ClientData clientData));
/* Procedure to call. */
ClientData clientData; /* Argument to pass to proc. */
Tk_TimerToken token; /* Identifies event so it can be
* deleted. */
struct TimerEvent *nextPtr; /* Next event in queue, or NULL for
* end of queue. */
} TimerEvent;
static TimerEvent *firstTimerHandlerPtr;
/* First event in queue. */
/*
* The information below is used to provide read, write, and
* exception masks to select during calls to Tk_DoOneEvent.
*/
static fd_mask ready[3*MASK_SIZE];
/* Masks passed to select and modified
* by kernel to indicate which files are
* actually ready. */
static fd_mask check[3*MASK_SIZE];
/* Temporary set of masks, built up during
* Tk_DoOneEvent, that reflects what files
* we should wait for in the next select
* (doesn't include things that we've been
* asked to ignore in this call). */
static int numFds = 0; /* Number of valid bits in mask
* arrays (this value is passed
* to select). */
/*
* For each file registered in a call to Tk_CreateFileHandler,
* and for each display that's currently active, there is one
* record of the following type. All of these records are
* chained together into a single list.
*/
typedef struct FileHandler {
int fd; /* POSIX file descriptor for file. */
fd_mask *readPtr; /* Pointer to word in ready array
* for this file's read mask bit. */
fd_mask *writePtr; /* Same for write mask bit. */
fd_mask *exceptPtr; /* Same for except mask bit. */
fd_mask *checkReadPtr; /* Pointer to word in check array for
* this file's read mask bit. */
fd_mask *checkWritePtr; /* Same for write mask bit. */
fd_mask *checkExceptPtr; /* Same for except mask bit. */
fd_mask bitSelect; /* Value to AND with *readPtr etc. to
* select just this file's bit. */
int mask; /* Mask of desired events: TK_READABLE, etc. */
Tk_FileProc *proc; /* Procedure to call, in the style of
* Tk_CreateFileHandler. This is NULL
* if the handler was created by
* Tk_CreateFileHandler2. */
Tk_FileProc2 *proc2; /* Procedure to call, in the style of
* Tk_CreateFileHandler2. NULL means that
* the handler was created by
* Tk_CreateFileHandler. */
ClientData clientData; /* Argument to pass to proc. */
struct FileHandler *nextPtr;/* Next in list of all files we
* care about (NULL for end of
* list). */
} FileHandler;
static FileHandler *firstFileHandlerPtr;
/* List of all file events. */
/*
* There is one of the following structures for each of the
* handlers declared in a call to Tk_DoWhenIdle. All of the
* currently-active handlers are linked together into a list.
*/
typedef struct IdleHandler {
void (*proc) _ANSI_ARGS_((ClientData clientData));
/* Procedure to call. */
ClientData clientData; /* Value to pass to proc. */
int generation; /* Used to distinguish older handlers from
* recently-created ones. */
struct IdleHandler *nextPtr;/* Next in list of active handlers. */
} IdleHandler;
static IdleHandler *idleList = NULL;
/* First in list of all idle handlers. */
static IdleHandler *lastIdlePtr = NULL;
/* Last in list (or NULL for empty list). */
static int idleGeneration = 0; /* Used to fill in the "generation" fields
* of IdleHandler structures. Increments
* each time Tk_DoOneEvent starts calling
* idle handlers, so that all old handlers
* can be called without calling any of the
* new ones created by old ones. */
/*
* The following procedure provides a secret hook for tkXEvent.c so that
* it can handle delayed mouse motion events at the right time.
*/
void (*tkDelayedEventProc) _ANSI_ARGS_((void)) = NULL;
/*
* One of the following structures exists for each file with a handler
* created by the "fileevent" command. Several of the fields are
* two-element arrays, in which the first element is used for read
* events and the second for write events.
*/
typedef struct FileEvent {
FILE *f; /* Stdio handle for file. */
Tcl_Interp *interps[2]; /* Interpreters in which to execute
* scripts. NULL means no handler
* for event. */
char *scripts[2]; /* Scripts to evaluate in response to
* events (malloc'ed). NULL means no
* handler for event. */
struct FileEvent *nextPtr; /* Next in list of all file events
* currently defined. */
} FileEvent;
static FileEvent *firstFileEventPtr = NULL;
/* First in list of all existing
* file events. */
/*
* The data structure below is used by the "after" command to remember
* the command to be executed later.
*/
typedef struct AfterInfo {
Tcl_Interp *interp; /* Interpreter in which to execute command. */
char *command; /* Command to execute. Malloc'ed, so must
* be freed when structure is deallocated. */
int id; /* Integer identifier for command; used to
* cancel it. */
Tk_TimerToken token; /* Used to cancel the "after" command. NULL
* means that the command is run as an
* idle handler rather than as a timer
* handler. */
struct AfterInfo *nextPtr; /* Next in list of all "after" commands for
* the application. */
} AfterInfo;
static AfterInfo *firstAfterPtr = NULL;
/* First in list of all pending "after"
* commands. */
/*
* The data structure below is used to report background errors. One
* such structure is allocated for each error; it holds information
* about the interpreter and the error until tkerror can be invoked
* later as an idle handler.
*/
typedef struct BgError {
Tcl_Interp *interp; /* Interpreter in which error occurred. NULL
* means this error report has been cancelled
* (a previous report generated a break). */
char *errorMsg; /* The error message (interp->result when
* the error occurred). Malloc-ed. */
char *errorInfo; /* Value of the errorInfo variable
* (malloc-ed). */
char *errorCode; /* Value of the errorCode variable
* (malloc-ed). */
struct BgError *nextPtr; /* Next in list of all pending error
* reports. */
} BgError;
static BgError *firstBgPtr = NULL;
/* First in list of all background errors
* waiting to be processed (NULL if none). */
static BgError *lastBgPtr = NULL;
/* First in list of all background errors
* waiting to be processed (NULL if none). */
/*
* Prototypes for procedures referenced only in this file:
*/
static void AfterProc _ANSI_ARGS_((ClientData clientData));
static void DeleteFileEvent _ANSI_ARGS_((FILE *f));
static int FileEventProc _ANSI_ARGS_((ClientData clientData,
int mask, int flags));
static void FreeAfterPtr _ANSI_ARGS_((AfterInfo *afterPtr));
static void HandleBgErrors _ANSI_ARGS_((ClientData clientData));
static int TkwaitCmd2 _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp, int argc, char **argv));
static int UpdateCmd2 _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp, int argc, char **argv));
static char * WaitVariableProc2 _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp, char *name1, char *name2,
int flags));
/*
*--------------------------------------------------------------
*
* Tk_CreateFileHandler --
*
* Arrange for a given procedure to be invoked whenever
* a given file becomes readable or writable.
*
* Results:
* None.
*
* Side effects:
* From now on, whenever the I/O channel given by fd becomes
* ready in the way indicated by mask, proc will be invoked.
* See the manual entry for details on the calling sequence
* to proc. If fd is already registered then the old mask
* and proc and clientData values will be replaced with
* new ones.
*
*--------------------------------------------------------------
*/
void
Tk_CreateFileHandler(fd, mask, proc, clientData)
int fd; /* Integer identifier for stream. */
int mask; /* OR'ed combination of TK_READABLE,
* TK_WRITABLE, and TK_EXCEPTION:
* indicates conditions under which
* proc should be called. TK_IS_DISPLAY
* indicates that this is a display and that
* clientData is the (Display *) for it,
* and that events should be handled
* automatically.*/
Tk_FileProc *proc; /* Procedure to call for each
* selected event. */
ClientData clientData; /* Arbitrary data to pass to proc. */
{
register FileHandler *filePtr;
int index;
if (fd >= OPEN_MAX) {
panic("Tk_CreatefileHandler can't handle file id %d", fd);
}
/*
* Make sure the file isn't already registered. Create a
* new record in the normal case where there's no existing
* record.
*/
for (filePtr = firstFileHandlerPtr; filePtr != NULL;
filePtr = filePtr->nextPtr) {
if (filePtr->fd == fd) {
break;
}
}
index = fd/(NBBY*sizeof(fd_mask));
if (filePtr == NULL) {
filePtr = (FileHandler *) ckalloc(sizeof(FileHandler));
filePtr->fd = fd;
filePtr->readPtr = &ready[index];
filePtr->writePtr = &ready[index+MASK_SIZE];
filePtr->exceptPtr = &ready[index+2*MASK_SIZE];
filePtr->checkReadPtr = &check[index];
filePtr->checkWritePtr = &check[index+MASK_SIZE];
filePtr->checkExceptPtr = &check[index+2*MASK_SIZE];
filePtr->bitSelect = 1 << (fd%(NBBY*sizeof(fd_mask)));
filePtr->nextPtr = firstFileHandlerPtr;
firstFileHandlerPtr = filePtr;
}
/*
* The remainder of the initialization below is done
* regardless of whether or not this is a new record
* or a modification of an old one.
*/
filePtr->mask = mask;
filePtr->proc = proc;
filePtr->proc2 = NULL;
filePtr->clientData = clientData;
if (numFds <= fd) {
numFds = fd+1;
}
}
/*
*--------------------------------------------------------------
*
* Tk_CreateFileHandler2 --
*
* Arrange for a given procedure to be invoked during the
* event loop to handle a particular file.
*
* Results:
* None.
*
* Side effects:
* In each pass through Tk_DoOneEvent, proc will be invoked to
* decide whether fd is "ready" and take appropriate action if
* it is. See the manual entry for details on the calling
* sequence to proc. If a handler for fd has already been
* registered then it is superseded by the new one.
*
*--------------------------------------------------------------
*/
void
Tk_CreateFileHandler2(fd, proc, clientData)
int fd; /* Integer identifier for stream. */
Tk_FileProc2 *proc; /* Procedure to call from the event
* dispatcher. */
ClientData clientData; /* Arbitrary data to pass to proc. */
{
register FileHandler *filePtr;
/*
* Let Tk_CreateFileHandler do all of the work of setting up
* the handler, then just modify things a bit after it returns.
*/
Tk_CreateFileHandler(fd, 0, (Tk_FileProc *) NULL, clientData);
for (filePtr = firstFileHandlerPtr; filePtr->fd != fd;
filePtr = filePtr->nextPtr) {
/* Empty loop body. */
}
filePtr->proc = NULL;
filePtr->proc2 = proc;
}
/*
*--------------------------------------------------------------
*
* Tk_DeleteFileHandler --
*
* Cancel a previously-arranged callback arrangement for
* a file.
*
* Results:
* None.
*
* Side effects:
* If a callback was previously registered on fd, remove it.
*
*--------------------------------------------------------------
*/
void
Tk_DeleteFileHandler(fd)
int fd; /* Stream id for which to remove
* callback procedure. */
{
register FileHandler *filePtr;
FileHandler *prevPtr;
/*
* Find the entry for the given file (and return if there
* isn't one).
*/
for (prevPtr = NULL, filePtr = firstFileHandlerPtr; ;
prevPtr = filePtr, filePtr = filePtr->nextPtr) {
if (filePtr == NULL) {
return;
}
if (filePtr->fd == fd) {
break;
}
}
/*
* Clean up information in the callback record.
*/
if (prevPtr == NULL) {
firstFileHandlerPtr = filePtr->nextPtr;
} else {
prevPtr->nextPtr = filePtr->nextPtr;
}
ckfree((char *) filePtr);
/*
* Recompute numFds.
*/
numFds = 0;
for (filePtr = firstFileHandlerPtr; filePtr != NULL;
filePtr = filePtr->nextPtr) {
if (numFds <= filePtr->fd) {
numFds = filePtr->fd+1;
}
}
}
/*
*--------------------------------------------------------------
*
* Tk_CreateTimerHandler --
*
* Arrange for a given procedure to be invoked at a particular
* time in the future.
*
* Results:
* The return value is a token for the timer event, which
* may be used to delete the event before it fires.
*
* Side effects:
* When milliseconds have elapsed, proc will be invoked
* exactly once.
*
*--------------------------------------------------------------
*/
Tk_TimerToken
Tk_CreateTimerHandler(milliseconds, proc, clientData)
int milliseconds; /* How many milliseconds to wait
* before invoking proc. */
Tk_TimerProc *proc; /* Procedure to invoke. */
ClientData clientData; /* Arbitrary data to pass to proc. */
{
register TimerEvent *timerPtr, *tPtr2, *prevPtr;
static int id = 0;
timerPtr = (TimerEvent *) ckalloc(sizeof(TimerEvent));
/*
* Compute when the event should fire.
*/
(void) gettimeofday(&timerPtr->time, (struct timezone *) NULL);
timerPtr->time.tv_sec += milliseconds/1000;
timerPtr->time.tv_usec += (milliseconds%1000)*1000;
if (timerPtr->time.tv_usec >= 1000000) {
timerPtr->time.tv_usec -= 1000000;
timerPtr->time.tv_sec += 1;
}
/*
* Fill in other fields for the event.
*/
timerPtr->proc = proc;
timerPtr->clientData = clientData;
id++;
timerPtr->token = (Tk_TimerToken) id;
/*
* Add the event to the queue in the correct position
* (ordered by event firing time).
*/
for (tPtr2 = firstTimerHandlerPtr, prevPtr = NULL; tPtr2 != NULL;
prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) {
if ((tPtr2->time.tv_sec > timerPtr->time.tv_sec)
|| ((tPtr2->time.tv_sec == timerPtr->time.tv_sec)
&& (tPtr2->time.tv_usec > timerPtr->time.tv_usec))) {
break;
}
}
if (prevPtr == NULL) {
timerPtr->nextPtr = firstTimerHandlerPtr;
firstTimerHandlerPtr = timerPtr;
} else {
timerPtr->nextPtr = prevPtr->nextPtr;
prevPtr->nextPtr = timerPtr;
}
return timerPtr->token;
}
/*
*--------------------------------------------------------------
*
* Tk_DeleteTimerHandler --
*
* Delete a previously-registered timer handler.
*
* Results:
* None.
*
* Side effects:
* Destroy the timer callback identified by TimerToken,
* so that its associated procedure will not be called.
* If the callback has already fired, or if the given
* token doesn't exist, then nothing happens.
*
*--------------------------------------------------------------
*/
void
Tk_DeleteTimerHandler(token)
Tk_TimerToken token; /* Result previously returned by
* Tk_DeleteTimerHandler. */
{
register TimerEvent *timerPtr, *prevPtr;
for (timerPtr = firstTimerHandlerPtr, prevPtr = NULL; timerPtr != NULL;
prevPtr = timerPtr, timerPtr = timerPtr->nextPtr) {
if (timerPtr->token != token) {
continue;
}
if (prevPtr == NULL) {
firstTimerHandlerPtr = timerPtr->nextPtr;
} else {
prevPtr->nextPtr = timerPtr->nextPtr;
}
ckfree((char *) timerPtr);
return;
}
}
/*
*--------------------------------------------------------------
*
* Tk_DoWhenIdle --
*
* Arrange for proc to be invoked the next time the
* system is idle (i.e., just before the next time
* that Tk_DoOneEvent would have to wait for something
* to happen).
*
* Results:
* None.
*
* Side effects:
* Proc will eventually be called, with clientData
* as argument. See the manual entry for details.
*
*--------------------------------------------------------------
*/
void
Tk_DoWhenIdle(proc, clientData)
Tk_IdleProc *proc; /* Procedure to invoke. */
ClientData clientData; /* Arbitrary value to pass to proc. */
{
register IdleHandler *idlePtr;
idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler));
idlePtr->proc = proc;
idlePtr->clientData = clientData;
idlePtr->generation = idleGeneration;
idlePtr->nextPtr = NULL;
if (lastIdlePtr == NULL) {
idleList = idlePtr;
} else {
lastIdlePtr->nextPtr = idlePtr;
}
lastIdlePtr = idlePtr;
}
/*
*----------------------------------------------------------------------
*
* Tk_CancelIdleCall --
*
* If there are any when-idle calls requested to a given procedure
* with given clientData, cancel all of them.
*
* Results:
* None.
*
* Side effects:
* If the proc/clientData combination were on the when-idle list,
* they are removed so that they will never be called.
*
*----------------------------------------------------------------------
*/
void
Tk_CancelIdleCall(proc, clientData)
Tk_IdleProc *proc; /* Procedure that was previously registered. */
ClientData clientData; /* Arbitrary value to pass to proc. */
{
register IdleHandler *idlePtr, *prevPtr;
IdleHandler *nextPtr;
for (prevPtr = NULL, idlePtr = idleList; idlePtr != NULL;
prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) {
while ((idlePtr->proc == proc)
&& (idlePtr->clientData == clientData)) {
nextPtr = idlePtr->nextPtr;
ckfree((char *) idlePtr);
idlePtr = nextPtr;
if (prevPtr == NULL) {
idleList = idlePtr;
} else {
prevPtr->nextPtr = idlePtr;
}
if (idlePtr == NULL) {
lastIdlePtr = prevPtr;
return;
}
}
}
}
/*
*--------------------------------------------------------------
*
* Tk_DoOneEvent --
*
* Process a single event of some sort. If there's no
* work to do, wait for an event to occur, then process
* it.
*
* Results:
* The return value is 1 if the procedure actually found
* an event to process. If no event was found then 0 is
* returned.
*
* Side effects:
* May delay execution of process while waiting for an
* X event, X error, file-ready event, or timer event.
* The handling of the event could cause additional
* side effects. Collapses sequences of mouse-motion
* events for the same window into a single event by
* delaying motion event processing.
*
*--------------------------------------------------------------
*/
int
Tk_DoOneEvent(flags)
int flags; /* Miscellaneous flag values: may be any
* combination of TK_DONT_WAIT, TK_X_EVENTS,
* TK_FILE_EVENTS, TK_TIMER_EVENTS, and
* TK_IDLE_EVENTS. */
{
register FileHandler *filePtr;
struct timeval curTime, timeout, *timeoutPtr;
int numFound, mask, anyFilesToWaitFor;
if ((flags & TK_ALL_EVENTS) == 0) {
flags |= TK_ALL_EVENTS;
}
/*
* Phase One: see if there's a ready file that was left over
* from before (i.e don't do a select, just check the bits from
* the last select).
*/
checkFiles:
if (tcl_AsyncReady) {
(void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
return 1;
}
memset((VOID *) check, 0, 3*MASK_SIZE*sizeof(fd_mask));
anyFilesToWaitFor = 0;
for (filePtr = firstFileHandlerPtr; filePtr != NULL;
filePtr = filePtr->nextPtr) {
mask = 0;
if (*filePtr->readPtr & filePtr->bitSelect) {
mask |= TK_READABLE;
*filePtr->readPtr &= ~filePtr->bitSelect;
}
if (*filePtr->writePtr & filePtr->bitSelect) {
mask |= TK_WRITABLE;
*filePtr->writePtr &= ~filePtr->bitSelect;
}
if (*filePtr->exceptPtr & filePtr->bitSelect) {
mask |= TK_EXCEPTION;
*filePtr->exceptPtr &= ~filePtr->bitSelect;
}
if (filePtr->proc2 != NULL) {
/*
* Handler created by Tk_CreateFileHandler2.
*/
mask = (*filePtr->proc2)(filePtr->clientData, mask, flags);
if (mask == TK_FILE_HANDLED) {
return 1;
}
} else {
/*
* Handler created by Tk_CreateFileHandler.
*/
if (!(flags & TK_FILE_EVENTS)) {
continue;
}
if (mask != 0) {
(*filePtr->proc)(filePtr->clientData, mask);
return 1;
}
mask = filePtr->mask;
}
if (mask != 0) {
anyFilesToWaitFor = 1;
if (mask & TK_READABLE) {
*filePtr->checkReadPtr |= filePtr->bitSelect;
}
if (mask & TK_WRITABLE) {
*filePtr->checkWritePtr |= filePtr->bitSelect;
}
if (mask & TK_EXCEPTION) {
*filePtr->checkExceptPtr |= filePtr->bitSelect;
}
}
}
/*
* Phase Two: get the current time and see if any timer
* events are ready to fire. If so, fire one and return.
*/
checkTime:
if ((firstTimerHandlerPtr != NULL) && (flags & TK_TIMER_EVENTS)) {
register TimerEvent *timerPtr = firstTimerHandlerPtr;
(void) gettimeofday(&curTime, (struct timezone *) NULL);
if ((timerPtr->time.tv_sec < curTime.tv_sec)
|| ((timerPtr->time.tv_sec == curTime.tv_sec)
&& (timerPtr->time.tv_usec < curTime.tv_usec))) {
firstTimerHandlerPtr = timerPtr->nextPtr;
(*timerPtr->proc)(timerPtr->clientData);
ckfree((char *) timerPtr);
return 1;
}
}
/*
* Phase Three: if there are DoWhenIdle requests pending (or
* if we're not allowed to block), then do a select with an
* instantaneous timeout. If a ready file is found, then go
* back to process it.
*/
if (((idleList != NULL) && (flags & TK_IDLE_EVENTS))
|| (flags & TK_DONT_WAIT)) {
if (flags & (TK_X_EVENTS|TK_FILE_EVENTS)) {
memcpy((VOID *) ready, (VOID *) check,
3*MASK_SIZE*sizeof(fd_mask));
timeout.tv_sec = timeout.tv_usec = 0;
numFound = select(numFds, (SELECT_MASK *) &ready[0],
(SELECT_MASK *) &ready[MASK_SIZE],
(SELECT_MASK *) &ready[2*MASK_SIZE], &timeout);
if (numFound <= 0) {
/*
* Some systems don't clear the masks after an error, so
* we have to do it here.
*/
memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask));
}
if ((numFound > 0) || ((numFound == -1) && (errno == EINTR))) {
goto checkFiles;
}
}
}
/*
* Phase Four: if there is a delayed motion event then call a procedure
* to handle it. Do it now, before calling any DoWhenIdle handlers,
* since the goal of idle handlers is to delay until after all pending
* events have been processed.
*
* The particular implementation of this (a procedure variable shared
* with tkXEvent.c) is a bit kludgy, but it allows this file to be used
* separately without any of the rest of Tk.
*/
if ((tkDelayedEventProc != NULL) && (flags & TK_X_EVENTS)) {
(*tkDelayedEventProc)();
return 1;
}
/*
* Phase Five: process all pending DoWhenIdle requests.
*/
if ((idleList != NULL) && (flags & TK_IDLE_EVENTS)) {
register IdleHandler *idlePtr;
int oldGeneration;
oldGeneration = idleList->generation;
idleGeneration++;
/*
* The code below is trickier than it may look, for the following
* reasons:
*
* 1. New handlers can get added to the list while the current
* one is being processed. If new ones get added, we don't
* want to process them during this pass through the list (want
* to check for other work to do first). This is implemented
* using the generation number in the handler: new handlers
* will have a different generation than any of the ones currently
* on the list.
* 2. The handler can call Tk_DoOneEvent, so we have to remove
* the hander from the list before calling it. Otherwise an
* infinite loop could result.
* 3. Tk_CancelIdleCall can be called to remove an element from
* the list while a handler is executing, so the list could
* change structure during the call.
*/
for (idlePtr = idleList;
((idlePtr != NULL) && (idlePtr->generation == oldGeneration));
idlePtr = idleList) {
idleList = idlePtr->nextPtr;
if (idleList == NULL) {
lastIdlePtr = NULL;
}
(*idlePtr->proc)(idlePtr->clientData);
ckfree((char *) idlePtr);
}
return 1;
}
/*
* Phase Six: do a select to wait for either one of the
* files to become ready or for the first timer event to
* fire. Then go back to process the event.
*/
if ((flags & TK_DONT_WAIT)
|| !(flags & (TK_TIMER_EVENTS|TK_FILE_EVENTS|TK_X_EVENTS))) {
return 0;
}
if ((firstTimerHandlerPtr == NULL) || !(flags & TK_TIMER_EVENTS)) {
timeoutPtr = NULL;
} else {
timeoutPtr = &timeout;
timeout.tv_sec = firstTimerHandlerPtr->time.tv_sec - curTime.tv_sec;
timeout.tv_usec = firstTimerHandlerPtr->time.tv_usec - curTime.tv_usec;
if (timeout.tv_usec < 0) {
timeout.tv_sec -= 1;
timeout.tv_usec += 1000000;
}
}
if ((timeoutPtr == NULL) && !anyFilesToWaitFor) {
return 0;
}
memcpy((VOID *) ready, (VOID *) check, 3*MASK_SIZE*sizeof(fd_mask));
numFound = select(numFds, (SELECT_MASK *) &ready[0],
(SELECT_MASK *) &ready[MASK_SIZE],
(SELECT_MASK *) &ready[2*MASK_SIZE], timeoutPtr);
if (numFound == -1) {
/*
* Some systems don't clear the masks after an error, so
* we have to do it here.
*/
memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask));
}
if (numFound == 0) {
goto checkTime;
}
goto checkFiles;
}
/*
*----------------------------------------------------------------------
*
* Tk_Sleep --
*
* Delay execution for the specified number of milliseconds.
*
* Results:
* None.
*
* Side effects:
* Time passes.
*
*----------------------------------------------------------------------
*/
void
Tk_Sleep(ms)
int ms; /* Number of milliseconds to sleep. */
{
static struct timeval delay;
delay.tv_sec = ms/1000;
delay.tv_usec = (ms%1000)*1000;
(void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
(SELECT_MASK *) 0, &delay);
}
/*
*----------------------------------------------------------------------
*
* Tk_BackgroundError --
*
* This procedure is invoked to handle errors that occur in Tcl
* commands that are invoked in "background" (e.g. from event or
* timer bindings).
*
* Results:
* None.
*
* Side effects:
* The command "tkerror" is invoked later as an idle handler to
* process the error, passing it the error message. If that fails,
* then an error message is output on stderr.
*
*----------------------------------------------------------------------
*/
void
Tk_BackgroundError(interp)
Tcl_Interp *interp; /* Interpreter in which an error has
* occurred. */
{
BgError *errPtr;
char *varValue;
/*
* The Tcl_AddErrorInfo call below (with an empty string) ensures that
* errorInfo gets properly set. It's needed in cases where the error
* came from a utility procedure like Tcl_GetVar instead of Tcl_Eval;
* in these cases errorInfo still won't have been set when this
* procedure is called.
*/
Tcl_AddErrorInfo(interp, "");
errPtr = (BgError *) ckalloc(sizeof(BgError));
errPtr->interp = interp;
errPtr->errorMsg = (char *) ckalloc((unsigned) (strlen(interp->result)
+ 1));
strcpy(errPtr->errorMsg, interp->result);
varValue = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
if (varValue == NULL) {
varValue = errPtr->errorMsg;
}
errPtr->errorInfo = (char *) ckalloc((unsigned) (strlen(varValue) + 1));
strcpy(errPtr->errorInfo, varValue);
varValue = Tcl_GetVar(interp, "errorCode", TCL_GLOBAL_ONLY);
if (varValue == NULL) {
varValue = "";
}
errPtr->errorCode = (char *) ckalloc((unsigned) (strlen(varValue) + 1));
strcpy(errPtr->errorCode, varValue);
errPtr->nextPtr = NULL;
if (firstBgPtr == NULL) {
firstBgPtr = errPtr;
Tk_DoWhenIdle(HandleBgErrors, (ClientData) NULL);
} else {
lastBgPtr->nextPtr = errPtr;
}
lastBgPtr = errPtr;
Tcl_ResetResult(interp);
}
/*
*----------------------------------------------------------------------
*
* HandleBgErrors --
*
* This procedure is invoked as an idle handler to process all of
* the accumulated background errors.
*
* Results:
* None.
*
* Side effects:
* Depends on what actions "tkerror" takes for the errors.
*
*----------------------------------------------------------------------
*/
static void
HandleBgErrors(clientData)
ClientData clientData; /* Not used. */
{
Tcl_Interp *interp;
char *command;
char *argv[2];
int code;
BgError *errPtr;
while (firstBgPtr != NULL) {
interp = firstBgPtr->interp;
if (interp == NULL) {
goto doneWithReport;
}
/*
* Restore important state variables to what they were at
* the time the error occurred.
*/
Tcl_SetVar(interp, "errorInfo", firstBgPtr->errorInfo,
TCL_GLOBAL_ONLY);
Tcl_SetVar(interp, "errorCode", firstBgPtr->errorCode,
TCL_GLOBAL_ONLY);
/*
* Create and invoke the tkerror command.
*/
argv[0] = "tkerror";
argv[1] = firstBgPtr->errorMsg;
command = Tcl_Merge(2, argv);
Tcl_AllowExceptions(interp);
code = Tcl_GlobalEval(interp, command);
ckfree(command);
if (code == TCL_ERROR) {
if (strcmp(interp->result, "\"tkerror\" is an invalid command name or ambiguous abbreviation") == 0) {
fprintf(stderr, "%s\n", firstBgPtr->errorInfo);
} else {
fprintf(stderr, "tkerror failed to handle background error.\n");
fprintf(stderr, " Original error: %s\n",
firstBgPtr->errorMsg);
fprintf(stderr, " Error in tkerror: %s\n", interp->result);
}
} else if (code == TCL_BREAK) {
/*
* Break means cancel any remaining error reports for this
* interpreter.
*/
for (errPtr = firstBgPtr; errPtr != NULL;
errPtr = errPtr->nextPtr) {
if (errPtr->interp == interp) {
errPtr->interp = NULL;
}
}
}
/*
* Discard the command and the information about the error report.
*/
doneWithReport:
ckfree(firstBgPtr->errorMsg);
ckfree(firstBgPtr->errorInfo);
ckfree(firstBgPtr->errorCode);
errPtr = firstBgPtr->nextPtr;
ckfree((char *) firstBgPtr);
firstBgPtr = errPtr;
}
lastBgPtr = NULL;
}
/*
*----------------------------------------------------------------------
*
* Tk_AfterCmd --
*
* This procedure is invoked to process the "after" Tcl command.
* See the user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
int
Tk_AfterCmd(clientData, interp, argc, argv)
ClientData clientData; /* Main window associated with
* interpreter. Not used.*/
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
/*
* The variable below is used to generate unique identifiers for
* after commands. This id can wrap around, which can potentially
* cause problems. However, there are not likely to be problems
* in practice, because after commands can only be requested to
* about a month in the future, and wrap-around is unlikely to
* occur in less than about 1-10 years. Thus it's unlikely that
* any old ids will still be around when wrap-around occurs.
*/
static int nextId = 1;
int ms, id;
AfterInfo *afterPtr;
if (argc < 2) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " milliseconds ?command? ?arg arg ...?\" or \"",
argv[0], " cancel id|command\"", (char *) NULL);
return TCL_ERROR;
}
if (isdigit(UCHAR(argv[1][0]))) {
if (Tcl_GetInt(interp, argv[1], &ms) != TCL_OK) {
return TCL_ERROR;
}
if (ms < 0) {
ms = 0;
}
if (argc == 2) {
Tk_Sleep(ms);
return TCL_OK;
}
afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
afterPtr->interp = interp;
if (argc == 3) {
afterPtr->command = (char *) ckalloc((unsigned)
(strlen(argv[2]) + 1));
strcpy(afterPtr->command, argv[2]);
} else {
afterPtr->command = Tcl_Concat(argc-2, argv+2);
}
afterPtr->id = nextId;
nextId += 1;
afterPtr->token = Tk_CreateTimerHandler(ms, AfterProc,
(ClientData) afterPtr);
afterPtr->nextPtr = firstAfterPtr;
firstAfterPtr = afterPtr;
sprintf(interp->result, "after#%d", afterPtr->id);
} else if (strncmp(argv[1], "cancel", strlen(argv[1])) == 0) {
char *arg;
if (argc < 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " cancel id|command\"", (char *) NULL);
return TCL_ERROR;
}
if (argc == 3) {
arg = argv[2];
} else {
arg = Tcl_Concat(argc-2, argv+2);
}
if (strncmp(arg, "after#", 6) == 0) {
if (Tcl_GetInt(interp, arg+6, &id) != TCL_OK) {
return TCL_ERROR;
}
for (afterPtr = firstAfterPtr; afterPtr != NULL;
afterPtr = afterPtr->nextPtr) {
if (afterPtr->id == id) {
break;
}
}
} else {
for (afterPtr = firstAfterPtr; afterPtr != NULL;
afterPtr = afterPtr->nextPtr) {
if (strcmp(afterPtr->command, arg) == 0) {
break;
}
}
}
if (arg != argv[2]) {
ckfree(arg);
}
if (afterPtr != NULL) {
if (afterPtr->token != NULL) {
Tk_DeleteTimerHandler(afterPtr->token);
} else {
Tk_CancelIdleCall(AfterProc, (ClientData) afterPtr);
}
FreeAfterPtr(afterPtr);
}
} else if (strncmp(argv[1], "idle", strlen(argv[1])) == 0) {
if (argc < 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " idle script script ...\"", (char *) NULL);
return TCL_ERROR;
}
afterPtr = (AfterInfo *) ckalloc((unsigned) (sizeof(AfterInfo)));
afterPtr->interp = interp;
if (argc == 3) {
afterPtr->command = (char *) ckalloc((unsigned)
(strlen(argv[2]) + 1));
strcpy(afterPtr->command, argv[2]);
} else {
afterPtr->command = Tcl_Concat(argc-2, argv+2);
}
afterPtr->id = nextId;
nextId += 1;
afterPtr->token = NULL;
afterPtr->nextPtr = firstAfterPtr;
firstAfterPtr = afterPtr;
Tk_DoWhenIdle(AfterProc, (ClientData) afterPtr);
sprintf(interp->result, "after#%d", afterPtr->id);
} else {
Tcl_AppendResult(interp, "bad argument \"", argv[1],
"\": must be cancel, idle, or a number", (char *) NULL);
return TCL_ERROR;
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* AfterProc --
*
* Timer callback to execute commands registered with the
* "after" command.
*
* Results:
* None.
*
* Side effects:
* Executes whatever command was specified. If the command
* returns an error, then the command "tkerror" is invoked
* to process the error; if tkerror fails then information
* about the error is output on stderr.
*
*----------------------------------------------------------------------
*/
static void
AfterProc(clientData)
ClientData clientData; /* Describes command to execute. */
{
AfterInfo *afterPtr = (AfterInfo *) clientData;
AfterInfo *prevPtr;
int result;
/*
* First remove the callback from our list of callbacks; otherwise
* someone could delete the callback while it's being executed, which
* could cause a core dump.
*/
if (firstAfterPtr == afterPtr) {
firstAfterPtr = afterPtr->nextPtr;
} else {
for (prevPtr = firstAfterPtr; prevPtr->nextPtr != afterPtr;
prevPtr = prevPtr->nextPtr) {
/* Empty loop body. */
}
prevPtr->nextPtr = afterPtr->nextPtr;
}
/*
* Execute the callback.
*/
result = Tcl_GlobalEval(afterPtr->interp, afterPtr->command);
if (result != TCL_OK) {
Tcl_AddErrorInfo(afterPtr->interp, "\n (\"after\" script)");
Tk_BackgroundError(afterPtr->interp);
}
/*
* Free the memory for the callback.
*/
ckfree(afterPtr->command);
ckfree((char *) afterPtr);
}
/*
*----------------------------------------------------------------------
*
* FreeAfterPtr --
*
* This procedure removes an "after" command from the list of
* those that are pending and frees its resources. This procedure
* does *not* cancel the timer handler; if that's needed, the
* caller must do it.
*
* Results:
* None.
*
* Side effects:
* The memory associated with afterPtr is released.
*
*----------------------------------------------------------------------
*/
static void
FreeAfterPtr(afterPtr)
AfterInfo *afterPtr; /* Command to be deleted. */
{
AfterInfo *prevPtr;
if (firstAfterPtr == afterPtr) {
firstAfterPtr = afterPtr->nextPtr;
} else {
for (prevPtr = firstAfterPtr; prevPtr->nextPtr != afterPtr;
prevPtr = prevPtr->nextPtr) {
/* Empty loop body. */
}
prevPtr->nextPtr = afterPtr->nextPtr;
}
ckfree(afterPtr->command);
ckfree((char *) afterPtr);
}
/*
*----------------------------------------------------------------------
*
* Tk_FileeventCmd --
*
* This procedure is invoked to process the "fileevent" Tcl
* command. See the user documentation for details on what it does.
* This command is based on Mark Diekhans' "addinput" command.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
int
Tk_FileeventCmd(clientData, interp, argc, argv)
ClientData clientData; /* Main window associated with interpreter.
* Not used.*/
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
FILE *f;
int index, fd, c;
size_t length;
FileEvent *fevPtr, *prevPtr;
/*
* Parse arguments.
*/
if ((argc != 3) && (argc != 4)) {
Tcl_AppendResult(interp, "wrong # args: must be \"", argv[0],
" fileId event ?script?", (char *) NULL);
return TCL_ERROR;
}
c = argv[2][0];
length = strlen(argv[2]);
if ((c == 'r') && (strncmp(argv[2], "readable", length) == 0)) {
index = 0;
} else if ((c == 'w') && (strncmp(argv[2], "writable", length) == 0)) {
index = 1;
} else {
Tcl_AppendResult(interp, "bad event name \"", argv[2],
"\": must be readable or writable", (char *) NULL);
return TCL_ERROR;
}
if (Tcl_GetOpenFile(interp, argv[1], index, 1, &f) != TCL_OK) {
return TCL_ERROR;
}
fd = fileno(f);
/*
* Locate an existing file handler for this file, if one exists,
* and make a new one if none currently exists.
*/
for (fevPtr = firstFileEventPtr; ; fevPtr = fevPtr->nextPtr) {
if (fevPtr == NULL) {
if ((argc == 3) || (argv[3][0] == 0)) {
return TCL_OK;
}
fevPtr = (FileEvent *) ckalloc(sizeof(FileEvent));
fevPtr->f = f;
fevPtr->interps[0] = NULL;
fevPtr->interps[1] = NULL;
fevPtr->scripts[0] = NULL;
fevPtr->scripts[1] = NULL;
fevPtr->nextPtr = firstFileEventPtr;
firstFileEventPtr = fevPtr;
Tk_CreateFileHandler2(fileno(f), FileEventProc,
(ClientData) fevPtr);
tcl_FileCloseProc = DeleteFileEvent;
break;
}
if (fevPtr->f == f) {
break;
}
}
/*
* If we're just supposed to return the current script, do so.
*/
if (argc == 3) {
if (fevPtr->scripts[index] != NULL) {
interp->result = fevPtr->scripts[index];
}
return TCL_OK;
}
/*
* If we're supposed to delete the event handler, do so.
*/
if (argv[3][0] == 0) {
if (fevPtr->scripts[index] != NULL) {
fevPtr->interps[index] = NULL;
ckfree(fevPtr->scripts[index]);
fevPtr->scripts[index] = NULL;
}
if ((fevPtr->scripts[0] == NULL) && (fevPtr->scripts[1] == NULL)) {
if (firstFileEventPtr == fevPtr) {
firstFileEventPtr = fevPtr->nextPtr;
} else {
for (prevPtr = firstFileEventPtr; prevPtr->nextPtr != fevPtr;
prevPtr = prevPtr->nextPtr) {
/* Empty loop body. */
}
prevPtr->nextPtr = fevPtr->nextPtr;
}
Tk_DeleteFileHandler(fileno(fevPtr->f));
ckfree((char *) fevPtr);
}
return TCL_OK;
}
/*
* This is a new handler being created. Save its script.
*/
fevPtr->interps[index] = interp;
if (fevPtr->scripts[index] != NULL) {
ckfree(fevPtr->scripts[index]);
}
fevPtr->scripts[index] = ckalloc((unsigned) (strlen(argv[3]) + 1));
strcpy(fevPtr->scripts[index], argv[3]);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* FileEventProc --
*
* This procedure is invoked by Tk's event loop to deal with file
* event bindings created by the "fileevent" command.
*
* Results:
* The return value is TK_FILE_HANDLED if the file was ready and
* a script was invoked to handle it. Otherwise an OR-ed combination
* of TK_READABLE and TK_WRITABLE is returned, indicating the events
* that should be checked in future calls to select.
*
* Side effects:
* Whatever the event script does.
*
*----------------------------------------------------------------------
*/
static int
FileEventProc(clientData, mask, flags)
ClientData clientData; /* Pointer to FileEvent structure for file. */
int mask; /* OR-ed combination of the bits TK_READABLE,
* TK_WRITABLE, and TK_EXCEPTION, indicating
* current state of file. */
int flags; /* Flag bits passed to Tk_DoOneEvent;
* contains bits such as TK_DONT_WAIT,
* TK_X_EVENTS, Tk_FILE_EVENTS, etc. */
{
FileEvent *fevPtr = (FileEvent *) clientData;
Tcl_DString script;
Tcl_Interp *interp;
FILE *f;
int code, checkMask;
if (!(flags & TK_FILE_EVENTS)) {
return 0;
}
/*
* The code here is a little tricky, because the script for an
* event could delete the event handler. Thus, after we call
* Tcl_GlobalEval we can't use fevPtr anymore. We also have to
* copy the script to make sure that it doesn't get freed while
* being evaluated.
*/
checkMask = 0;
f = fevPtr->f;
if (fevPtr->scripts[1] != NULL) {
if (mask & TK_WRITABLE) {
Tcl_DStringInit(&script);
Tcl_DStringAppend(&script, fevPtr->scripts[1], -1);
interp = fevPtr->interps[1];
code = Tcl_GlobalEval(interp, Tcl_DStringValue(&script));
Tcl_DStringFree(&script);
if (code != TCL_OK) {
goto error;
}
return TK_FILE_HANDLED;
} else {
checkMask |= TK_WRITABLE;
}
}
if (fevPtr->scripts[0] != NULL) {
if ((mask & TK_READABLE) || TK_READ_DATA_PENDING(f)) {
Tcl_DStringInit(&script);
Tcl_DStringAppend(&script, fevPtr->scripts[0], -1);
interp = fevPtr->interps[0];
code = Tcl_GlobalEval(interp, Tcl_DStringValue(&script));
Tcl_DStringFree(&script);
if (code != TCL_OK) {
goto error;
}
return TK_FILE_HANDLED;
} else {
checkMask |= TK_READABLE;
}
}
return checkMask;
/*
* An error occurred in the script, so we have to call
* Tk_BackgroundError. However, it's possible that the file ready
* condition didn't get cleared for the file, so we could end
* up in an infinite loop if we're not careful. To be safe,
* delete the event handler.
*/
error:
DeleteFileEvent(f);
Tcl_AddErrorInfo(interp,
"\n (script bound to file event - binding deleted)");
Tk_BackgroundError(interp);
return TK_FILE_HANDLED;
}
/*
*----------------------------------------------------------------------
*
* DeleteFileEvent --
*
* This procedure is invoked to delete all file event handlers
* for a file. For example, this is necessary if a file is closed,
* or if an error occurs in a handler for a file.
*
* Results:
* None.
*
* Side effects:
* The file event handler is removed, so it will never be invoked
* again.
*
*----------------------------------------------------------------------
*/
static void
DeleteFileEvent(f)
FILE *f; /* Stdio structure describing open file. */
{
register FileEvent *fevPtr;
FileEvent *prevPtr;
/*
* See if there exists a file handler for the given file.
*/
for (prevPtr = NULL, fevPtr = firstFileEventPtr; ;
prevPtr = fevPtr, fevPtr = fevPtr->nextPtr) {
if (fevPtr == NULL) {
return;
}
if (fevPtr->f == f) {
break;
}
}
/*
* Unlink it from the list, then free it.
*/
if (prevPtr == NULL) {
firstFileEventPtr = fevPtr->nextPtr;
} else {
prevPtr->nextPtr = fevPtr->nextPtr;
}
Tk_DeleteFileHandler(fileno(fevPtr->f));
if (fevPtr->scripts[0] != NULL) {
ckfree(fevPtr->scripts[0]);
}
if (fevPtr->scripts[1] != NULL) {
ckfree(fevPtr->scripts[1]);
}
ckfree((char *) fevPtr);
}
/*
*----------------------------------------------------------------------
*
* TkEventCleanupProc --
*
* This procedure is invoked whenever an interpreter is deleted.
* It deletes any file events and after commands that refer to
* that interpreter.
*
* Results:
* None.
*
* Side effects:
* File event handlers and after commands are removed.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
void
TkEventCleanupProc(clientData, interp)
ClientData clientData; /* Not used. */
Tcl_Interp *interp; /* Interpreter that is being deleted. */
{
FileEvent *fevPtr, *prevPtr, *nextPtr;
AfterInfo *afterPtr, *prevAfterPtr, *nextAfterPtr;
int i;
prevPtr = NULL;
fevPtr = firstFileEventPtr;
while (fevPtr != NULL) {
for (i = 0; i < 2; i++) {
if (fevPtr->interps[i] == interp) {
fevPtr->interps[i] = NULL;
ckfree((char *) fevPtr->scripts[i]);
fevPtr->scripts[i] = NULL;
}
}
if ((fevPtr->scripts[0] != NULL) || (fevPtr->scripts[1] != NULL)) {
prevPtr = fevPtr;
fevPtr = fevPtr->nextPtr;
continue;
}
nextPtr = fevPtr->nextPtr;
if (prevPtr == NULL) {
firstFileEventPtr = nextPtr;
} else {
prevPtr->nextPtr = nextPtr;
}
Tk_DeleteFileHandler(fileno(fevPtr->f));
ckfree((char *) fevPtr);
fevPtr = nextPtr;
}
prevAfterPtr = NULL;
afterPtr = firstAfterPtr;
while (afterPtr != NULL) {
if (afterPtr->interp != interp) {
prevAfterPtr = afterPtr;
afterPtr = afterPtr->nextPtr;
continue;
}
nextAfterPtr = afterPtr->nextPtr;
if (prevAfterPtr == NULL) {
firstAfterPtr = nextAfterPtr;
} else {
prevAfterPtr->nextPtr = nextAfterPtr;
}
if (afterPtr->token != NULL) {
Tk_DeleteTimerHandler(afterPtr->token);
} else {
Tk_CancelIdleCall(AfterProc, (ClientData) afterPtr);
}
ckfree(afterPtr->command);
ckfree((char *) afterPtr);
afterPtr = nextAfterPtr;
}
}
/*
*----------------------------------------------------------------------
*
* TkwaitCmd2 --
*
* This procedure is invoked to process the "tkwait" Tcl command.
* See the user documentation for details on what it does. This
* is a modified version of tkwait with only the "variable"
* option, suitable for use in stand-alone mode without the rest
* of Tk. It's only used when Tk_EventInit has been called.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
static int
TkwaitCmd2(clientData, interp, argc, argv)
ClientData clientData; /* Not used. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
int c, done;
size_t length;
if (argc != 3) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " variable name\"", (char *) NULL);
return TCL_ERROR;
}
c = argv[1][0];
length = strlen(argv[1]);
if ((c == 'v') && (strncmp(argv[1], "variable", length) == 0)
&& (length >= 2)) {
Tcl_TraceVar(interp, argv[2],
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
WaitVariableProc2, (ClientData) &done);
done = 0;
while (!done) {
Tk_DoOneEvent(0);
}
Tcl_UntraceVar(interp, argv[2],
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
WaitVariableProc2, (ClientData) &done);
} else {
Tcl_AppendResult(interp, "bad option \"", argv[1],
"\": must be variable", (char *) NULL);
return TCL_ERROR;
}
/*
* Clear out the interpreter's result, since it may have been set
* by event handlers.
*/
Tcl_ResetResult(interp);
return TCL_OK;
}
/* ARGSUSED */
static char *
WaitVariableProc2(clientData, interp, name1, name2, flags)
ClientData clientData; /* Pointer to integer to set to 1. */
Tcl_Interp *interp; /* Interpreter containing variable. */
char *name1; /* Name of variable. */
char *name2; /* Second part of variable name. */
int flags; /* Information about what happened. */
{
int *donePtr = (int *) clientData;
*donePtr = 1;
return (char *) NULL;
}
/*
*----------------------------------------------------------------------
*
* UpdateCmd2 --
*
* This procedure is invoked to process the "update" Tcl command.
* See the user documentation for details on what it does. This
* is a modified version of the command that doesn't deal with
* windows, suitable for use in stand-alone mode without the rest
* of Tk. It's only used when Tk_EventInit has been called.
*
* Results:
* A standard Tcl result.
*
* Side effects:
* See the user documentation.
*
*----------------------------------------------------------------------
*/
/* ARGSUSED */
static int
UpdateCmd2(clientData, interp, argc, argv)
ClientData clientData; /* Not used. */
Tcl_Interp *interp; /* Current interpreter. */
int argc; /* Number of arguments. */
char **argv; /* Argument strings. */
{
int flags;
if (argc == 1) {
flags = TK_DONT_WAIT|TK_FILE_EVENTS|TK_TIMER_EVENTS|TK_IDLE_EVENTS;
} else if (argc == 2) {
if (strncmp(argv[1], "idletasks", strlen(argv[1])) != 0) {
Tcl_AppendResult(interp, "bad argument \"", argv[1],
"\": must be idletasks", (char *) NULL);
return TCL_ERROR;
}
flags = TK_IDLE_EVENTS;
} else {
Tcl_AppendResult(interp, "wrong # args: should be \"",
argv[0], " ?idletasks?\"", (char *) NULL);
return TCL_ERROR;
}
/*
* Handle all pending events.
*/
while (Tk_DoOneEvent(flags) != 0) {
/* Empty loop body */
}
/*
* Must clear the interpreter's result because event handlers could
* have executed commands.
*/
Tcl_ResetResult(interp);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* Tk_EventInit --
*
* This procedure is invoked from Tcl_AppInit if the Tk event stuff
* is being used by itself (without the rest of Tk) in an application.
* It creates the "after" and "fileevent" commands.
*
* Results:
* Always returns TCL_OK.
*
* Side effects:
* New commands get added to interp.
*
*----------------------------------------------------------------------
*/
int
Tk_EventInit(interp)
Tcl_Interp *interp; /* Interpreter in which to set up
* event-handling. */
{
Tcl_CreateCommand(interp, "after", Tk_AfterCmd, (ClientData) NULL,
(void (*)()) NULL);
Tcl_CreateCommand(interp, "fileevent", Tk_FileeventCmd, (ClientData) NULL,
(void (*)()) NULL);
Tcl_CreateCommand(interp, "tkwait", TkwaitCmd2, (ClientData) NULL,
(void (*)()) NULL);
Tcl_CreateCommand(interp, "update", UpdateCmd2, (ClientData) NULL,
(void (*)()) NULL);
Tcl_CallWhenDeleted(interp, TkEventCleanupProc, (ClientData) NULL);
return TCL_OK;
}